/* Copyright (c) Microsoft Corporation.
   Licensed under the MIT License. */

/***************************************************************************

	actrsave.cpp: Actor load/save code

	Primary Author: ******
	Review Status: REVIEWED - any changes to this file must be reviewed!

	Here's the chunk hierarchy:

	ACTR // contains an ACTF (origin, arid, nfrmFirst, tagTmpl...)
	 |
	 +---PATH (chid 0) // _pglxyz (actor path)
	 |
	 +---GGAE (chid 0) // _pggaev (actor events)
	

***************************************************************************/
#include "soc.h"
ASSERTNAME

const CHID kchidPath = 0;
const CHID kchidGgae = 0;

struct ACTF // Actor chunk on file
	{
	short bo;		// Byte order
	short osk;		// OS kind
	XYZ dxyzFullRte;	// Translation of the route
	long arid;		// Unique id assigned to this actor.
	long nfrmFirst;	// First frame in this actor's stage life
	long nfrmLast;	// Last frame in this actor's stage life
	TAG tagTmpl;	// Tag to actor's template
	};
const BOM kbomActf = 0x5ffc0000 | kbomTag;

/***************************************************************************
	Write the actor out to disk.  Store the root chunk in the given CNO.
	If this function returns false, it is the client's responsibility to
	delete the actor chunks.
***************************************************************************/
bool ACTR::FWrite(PCFL pcfl, CNO cnoActr, CNO cnoScene)
{
	AssertThis(0);
	AssertPo(pcfl, 0);

 	ACTF actf;
	CNO cnoPath;
	CNO cnoGgae;
	CNO cnoTmpl;
	BLCK blck;
	KID kid;
	long iaev;
	AEV *paev;
	AEVSND aevsnd;
	long nfrmFirst;
	long nfrmLast;

	// Validate the actor's lifetime if not done already
	if (knfrmInvalid != _nfrmFirst)
		{
		if (!FGetLifetime(&nfrmFirst, &nfrmLast))
			return fFalse;
		}
#ifdef DEBUG
	if (knfrmInvalid == _nfrmFirst)
		Warn("Dev: Why are we saving an actor who has no first frame number?");
#endif //DEBUG

	// Save and adopt TMPL chunk if it's a ksidUseCrf chunk
	if (_tagTmpl.sid == ksidUseCrf)
		{
		Assert(_ptmpl->FIsTdt(), "only TDTs should be embedded in user doc");
		if (!pcfl->FFind(_tagTmpl.ctg, _tagTmpl.cno))
			{
			if (!((PTDT)_ptmpl)->FWrite(pcfl, _tagTmpl.ctg, &cnoTmpl))
				return fFalse;
			// Keep CNO the same
			pcfl->Move(_tagTmpl.ctg, cnoTmpl, _tagTmpl.ctg, _tagTmpl.cno);
			}

		if (tNo == pcfl->TIsDescendent(kctgActr, cnoActr, _tagTmpl.ctg,
			_tagTmpl.cno))
			{
			if (!pcfl->FAdoptChild(kctgActr, cnoActr, _tagTmpl.ctg,
				_tagTmpl.cno)) // clears loner bit
				{
				return fFalse;
				}
			}
		}

	// Write the ACTR chunk:
	actf.bo = kboCur;
	actf.osk = koskCur;
	actf.dxyzFullRte = _dxyzFullRte;
	actf.arid = _arid;
	actf.nfrmFirst = _nfrmFirst;
	actf.nfrmLast = _nfrmLast;
	actf.tagTmpl = _tagTmpl;
	if (!pcfl->FPutPv(&actf, size(ACTF), kctgActr, cnoActr))
		return fFalse;

	// Now write the PATH chunk:
	if (!pcfl->FAddChild(kctgActr, cnoActr, kchidPath, _pglrpt->CbOnFile(),
		kctgPath, &cnoPath, &blck))
		{
		return fFalse;
		}
	if (!_pglrpt->FWrite(&blck))
		return fFalse;

	// Now write the GGAE chunk:
	if (!pcfl->FAddChild(kctgActr, cnoActr, kchidGgae, _pggaev->CbOnFile(),
		kctgGgae, &cnoGgae, &blck))
		{
		return fFalse;
		}
	if (!_pggaev->FWrite(&blck))
		return fFalse;

	// Adopt actor sounds into the scene
	for (iaev = 0; iaev < _pggaev->IvMac(); iaev++)
		{
		paev = (AEV *)(_pggaev->QvFixedGet(iaev));
		if (aetSnd != paev->aet)
			continue;
		_pggaev->Get(iaev, &aevsnd);
		if (aevsnd.tag.sid != ksidUseCrf)
			continue;

		// For user sounds, the tag's cno must already be correct.
		// Moreover, FResolveSndTag can't succeed if the msnd chunk is
		// not yet a child of the current scene.

		// If the msnd chunk already exists as this chid of this scene, continue
		if (pcfl->FGetKidChidCtg(kctgScen, cnoScene, aevsnd.chid, kctgMsnd, &kid))
			continue;

		// If the msnd does not exist in this file, it exists in the main movie
		if (!pcfl->FFind(kctgMsnd, aevsnd.tag.cno))
		   	continue;

		// The msnd chunk has not been adopted into the scene as the specified chid
		if (!pcfl->FAdoptChild(kctgScen, cnoScene, kctgMsnd,
			aevsnd.tag.cno, aevsnd.chid))
			{
			return fFalse;
			}
		}

	return fTrue;
}


/***************************************************************************
	Read the actor data from disk, (re-)construct the actor, and return a
	pointer to it.
***************************************************************************/
PACTR ACTR::PactrRead(PCRF pcrf, CNO cnoActr)
{
	AssertPo(pcrf, 0);

	ACTR *pactr;
	KID kid;
	PCFL pcfl = pcrf->Pcfl();

	pactr = NewObj ACTR;
	if (pvNil == pactr)
		goto LFail;
	if (!pactr->_FReadActor(pcfl, cnoActr))
		goto LFail;
	if (!pcfl->FGetKidChidCtg(kctgActr, cnoActr, kchidPath, kctgPath, &kid))
		goto LFail;
	if (!pactr->_FReadRoute(pcfl, kid.cki.cno))
		goto LFail;
	if (!pcfl->FGetKidChidCtg(kctgActr, cnoActr, kchidGgae, kctgGgae, &kid))
		goto LFail;
	if (!pactr->_FReadEvents(pcfl, kid.cki.cno))
		goto LFail;
	if (!pactr->_FOpenTags(pcrf))
		goto LFail;
	if (pvNil == (pactr->_pglsmm = GL::PglNew(size(SMM), kcsmmGrow)))
		goto LFail;
	pactr->_pglsmm->SetMinGrow(kcsmmGrow);

	// Now that the tags are open, fetch the TMPL
	pactr->_ptmpl = (PTMPL)vptagm->PbacoFetch(&pactr->_tagTmpl, TMPL::FReadTmpl);
	if (pvNil == pactr->_ptmpl)
		goto LFail;

	if (knfrmInvalid == pactr->_nfrmLast && knfrmInvalid != pactr->_nfrmFirst)
		{
		long nfrmFirst, nfrmLast;

		if (!pactr->FGetLifetime(&nfrmFirst, &nfrmLast))
			goto LFail;
		}

	AssertPo(pactr, 0);
	return pactr;
LFail:
	Warn("PactrRead failed");
	ReleasePpo(&pactr);
	return pvNil;
}


/***************************************************************************
	Read the ACTF. This handles converting an ACTF that doesn't have an
	nfrmLast.
***************************************************************************/
bool _FReadActf(PBLCK pblck, ACTF *pactf)
{
	AssertPo(pblck, 0);
	AssertVarMem(pactf);
	bool fOldActf = fFalse;

	if (!pblck->FUnpackData())
		return fFalse;

	if (pblck->Cb() != size(ACTF))
		{
		if (pblck->Cb() != size(ACTF) - size(long))
			return fFalse;
		fOldActf = fTrue;
		}

	if (!pblck->FReadRgb(pactf, pblck->Cb(), 0))
		return fFalse;

	if (fOldActf)
		{
		BltPb(&pactf->nfrmLast, &pactf->nfrmLast + 1,
			size(ACTF) - offset(ACTF, nfrmLast) - size(long));
		}

	if (kboOther == pactf->bo)
		SwapBytesBom(pactf, kbomActf);
	if (kboCur != pactf->bo)
		{
		Bug("Corrupt ACTF");
		return fFalse;
		}

	if (fOldActf)
		pactf->nfrmLast = knfrmInvalid;
	return fTrue;
}


/***************************************************************************
	Read the ACTR chunk
***************************************************************************/
bool ACTR::_FReadActor(PCFL pcfl, CNO cno)
{
	AssertBaseThis(0);
	AssertPo(pcfl, 0);

	ACTF actf;
	BLCK blck;

	if (!pcfl->FFind(kctgActr, cno, &blck) || !_FReadActf(&blck, &actf))
		return fFalse;

	Assert(kboCur == actf.bo, "bad ACTF");
	_dxyzFullRte = actf.dxyzFullRte;
	_arid = actf.arid;
	_nfrmFirst = actf.nfrmFirst;
	_nfrmLast = actf.nfrmLast;
	_tagTmpl = actf.tagTmpl;
	_fLifeDirty = (knfrmInvalid == _nfrmFirst) || (knfrmInvalid == _nfrmLast);

	if (_tagTmpl.sid == ksidUseCrf)
		{
		// Actor is a TDT.  Tag might be wrong if this actor was imported,
		// so look for child TMPL.
		KID kid;

		if (!pcfl->FGetKidChidCtg(kctgActr, cno, 0, kctgTmpl, &kid))
			{
			Bug("where's the child TMPL?");
			return fTrue; // hope the tag is correct
			}
		_tagTmpl.cno = kid.cki.cno;
		}

	return fTrue;
}


/******************************************************************************
	FAdjustAridOnFile
		Given a chunky file, a CNO and a delta for the arid, updates the
		arid for the actor on file.
	
	Arguments:
		PCFL pcfl   -- the file the actor's on
		CNO cno     -- the CNO of the actor
		long darid  -- the change of the arid
	
	Returns: fTrue if everything went well, fFalse otherwise
	
************************************************************ PETED ***********/
bool ACTR::FAdjustAridOnFile(PCFL pcfl, CNO cno, long darid)
{
	AssertPo(pcfl, 0);
	Assert(darid != 0, "Why call this with darid == 0?");

	ACTF actf;
	BLCK blck;

	if (!pcfl->FFind(kctgActr, cno, &blck) || !_FReadActf(&blck, &actf))
		return fFalse;

	Assert(kboCur == actf.bo, "bad ACTF");
	actf.arid += darid;
	return pcfl->FPutPv(&actf, size(ACTF), kctgActr, cno);
}


/***************************************************************************
	Read the PATH (_pglrpt) chunk
***************************************************************************/
bool ACTR::_FReadRoute(PCFL pcfl, CNO cno)
{
	AssertBaseThis(0);
	AssertPo(pcfl, 0);

	BLCK blck;
	short bo;

	if (!pcfl->FFind(kctgPath, cno, &blck))
		return fFalse;
	_pglrpt = GL::PglRead(&blck, &bo);
	if (pvNil == _pglrpt)
		return fFalse;
	AssertBomRglw(kbomRpt, size(RPT));
	if (kboOther == bo)
		{
		SwapBytesRglw(_pglrpt->QvGet(0),
			LwMul(_pglrpt->IvMac(), size(RPT) / size(long)));
		}
	return fTrue;
}


/***************************************************************************
	Read the GGAE (_pggaev) chunk
***************************************************************************/
bool ACTR::_FReadEvents(PCFL pcfl, CNO cno)
{
	AssertBaseThis(0);
	AssertPo(pcfl, 0);

	BLCK blck;
	short bo;

	if (!pcfl->FFind(kctgGgae, cno, &blck))
		return fFalse;
	_pggaev = GG::PggRead(&blck, &bo);
	if (pvNil == _pggaev)
		return fFalse;
	if (kboOther == bo)
		_SwapBytesPggaev(_pggaev);
	return fTrue;
}


/***************************************************************************
	SwapBytes all events in pggaev
***************************************************************************/
void ACTR::_SwapBytesPggaev(PGG pggaev)
{
	AssertPo(pggaev, 0);

	long iaev;

	for (iaev = 0; iaev < pggaev->IvMac(); iaev++)
		{
		SwapBytesBom(pggaev->QvFixedGet(iaev), kbomAev);
		switch(((AEV *)pggaev->QvFixedGet(iaev))->aet)
			{
		case aetCost:
			SwapBytesBom(pggaev->QvGet(iaev), kbomAevcost);
			break;
		case aetSnd:
			SwapBytesBom(pggaev->QvGet(iaev), kbomAevsnd);
			break;
		case aetSize:
			SwapBytesBom(pggaev->QvGet(iaev), kbomAevsize);
			break;
		case aetPull:
			SwapBytesBom(pggaev->QvGet(iaev), kbomAevpull);
			break;
		case aetRotF:
		case aetRotH:
			SwapBytesBom(pggaev->QvGet(iaev), kbomAevrot);
			break;
		case aetActn:
			SwapBytesBom(pggaev->QvGet(iaev), kbomAevactn);
			break;
		case aetAdd:
			SwapBytesBom(pggaev->QvGet(iaev), kbomAevadd);
			break;
		case aetFreeze:
			SwapBytesBom(pggaev->QvGet(iaev), kbomAevfreeze);
			break;
		case aetMove:
			SwapBytesBom(pggaev->QvGet(iaev), kbomAevmove);
			break;
		case aetTweak:
			SwapBytesBom(pggaev->QvGet(iaev), kbomAevtweak);
			break;
		case aetStep:
			SwapBytesBom(pggaev->QvGet(iaev), kbomAevstep);
			break;
		case aetRem:
			// no var data
			break;
	    default:
	        Bug("Unknown AET");
			break;
			}
		}
}

/***************************************************************************
	Open all tags for this actor
***************************************************************************/
bool ACTR::_FOpenTags(PCRF pcrf)
{
	AssertBaseThis(0);
	AssertPo(pcrf, 0);

	long iaev = 0;
	PTAG ptag;

	if (!TAGM::FOpenTag(&_tagTmpl, pcrf))
		goto LFail;

	_pggaev->Lock();
    for (iaev = 0; iaev < _pggaev->IvMac(); iaev++)
		{
		if (_FIsIaevTag(_pggaev, iaev, &ptag))
			{
			if (!TAGM::FOpenTag(ptag, pcrf))
				goto LFail;
			}
		}
	_pggaev->Unlock();
	return fTrue;
LFail:
	// Close the tags that were opened before failure
	while (--iaev >= 0)
		{
		if (_FIsIaevTag(_pggaev, iaev, &ptag))
			TAGM::CloseTag(ptag);
		}
	_pggaev->Unlock();
	return fFalse;
}


/***************************************************************************
	Close all tags in this actor's event stream
***************************************************************************/
void ACTR::_CloseTags(void)
{
	AssertBaseThis(0); // because destructor calls this function

	long iaev;
	PTAG ptag;

	TAGM::CloseTag(&_tagTmpl);

	if (pvNil == _pggaev)
		return;

	_pggaev->Lock();
    for (iaev = 0; iaev < _pggaev->IvMac(); iaev++)
		{
		if (_FIsIaevTag(_pggaev, iaev, &ptag))
			TAGM::CloseTag(ptag);
		}
	_pggaev->Unlock();
	return;
}


/***************************************************************************
	Get all the tags that the actor uses
***************************************************************************/
PGL ACTR::PgltagFetch(PCFL pcfl, CNO cno, bool *pfError)
{
	AssertPo(pcfl, 0);
	AssertVarMem(pfError);

	ACTF actf;
	BLCK blck;
	short bo;
	PTAG ptag;
	PGL pgltag;
	PGG pggaev = pvNil;
	long iaev;
	KID kid;

	pgltag = GL::PglNew(size(TAG), 0);
	if (pvNil == pgltag)
		goto LFail;
	
	// Read the ACTF so we can insert tagTmpl:
	if (!pcfl->FFind(kctgActr, cno, &blck) || !_FReadActf(&blck, &actf))
		goto LFail;

	if (actf.tagTmpl.sid == ksidUseCrf)
		{
		PGL pgltagTmpl;
	
		// Actor is a TDT.  Tag might be wrong if this actor was imported,
		// so look for child TMPL.
		if (pcfl->FGetKidChidCtg(kctgActr, cno, 0, kctgTmpl, &kid))
			{
			actf.tagTmpl.cno = kid.cki.cno;
			}
		else
			{
			Bug("where's the child TMPL?");
			}
			
		pgltagTmpl = TMPL::PgltagFetch(pcfl, actf.tagTmpl.ctg,
			actf.tagTmpl.cno, pfError);
		if (*pfError)
			{
			ReleasePpo(&pgltagTmpl);
			goto LFail;
			}
		if (pvNil != pgltagTmpl)
			{
			long itag;
			TAG tag;

			for (itag = 0; itag < pgltagTmpl->IvMac(); itag++)
				{
				pgltagTmpl->Get(itag, &tag);
				if (!pgltag->FAdd(&tag))
					{
					ReleasePpo(&pgltagTmpl);
					goto LFail;
					}
				}
			ReleasePpo(&pgltagTmpl);
			}
		}

	if (!pgltag->FInsert(0, &actf.tagTmpl))
		goto LFail;

    // Pull all tags out of the event list:
	if (!pcfl->FGetKidChidCtg(kctgActr, cno, kchidGgae, kctgGgae, &kid))
		goto LFail;
	if (!pcfl->FFind(kctgGgae, kid.cki.cno, &blck))
		goto LFail;
	pggaev = GG::PggRead(&blck, &bo);
	if (pvNil == pggaev)
		goto LFail;
	if (kboOther == bo)
		_SwapBytesPggaev(pggaev);
	pggaev->Lock();
    for (iaev = 0; iaev < pggaev->IvMac(); iaev++)
		{
		if (_FIsIaevTag(pggaev, iaev, &ptag))
			{
			if (!pgltag->FAdd(ptag))
				{
				pggaev->Unlock();
				goto LFail;
				}
			}
		}
	pggaev->Unlock();
    *pfError = fFalse;
	ReleasePpo(&pggaev);
	return pgltag;
LFail:
    *pfError = fTrue;
	ReleasePpo(&pgltag);
	ReleasePpo(&pggaev);
	return pvNil;
}


/***************************************************************************
	If the iaev'th event of pggaev has a tag, sets *pptag to point to it.
	WARNING: unless you locked pggaev, *pptag is a qtag!
***************************************************************************/
bool ACTR::_FIsIaevTag(PGG pggaev, long iaev, PTAG *pptag, PAEV *pqaev)
{
	AssertPo(pggaev, 0);
	AssertIn(iaev, 0, pggaev->IvMac());
	AssertVarMem(pptag);
	AssertNilOrVarMem(pqaev);

	AEV *qaev;
    qaev = (AEV *)pggaev->QvFixedGet(iaev);
	if (pqaev != pvNil)
		*pqaev = qaev;

	switch (qaev->aet)
		{
	case aetCost:
		if (!((AEVCOST *)pggaev->QvGet(iaev))->fCmtl)
			{
			*pptag = &((AEVCOST *)pggaev->QvGet(iaev))->tag;
			return fTrue;
			}
		break;
	case aetSnd:
		*pptag = &((AEVSND *)pggaev->QvGet(iaev))->tag;
		return fTrue;
	case aetSize:
	case aetPull:
	case aetRotF:
	case aetRotH:
	case aetActn:
	case aetAdd:
	case aetFreeze:
	case aetTweak:
	case aetStep:
	case aetRem:
	case aetMove:
		break;
    default:
        Bug("Unknown AET");
		break;
		}
	*pptag = pvNil;
	return fFalse;
}
